package com.apigateway.init;

import apigateway.serviceRegister.ServiceNode;
import com.apigateway.mapper.ApiServiceMapper;
import com.apigateway.model.ApiService;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

/**
 *
 * initServiceData在容器初始化时用来获取zk中的服务节点，并对每个节点设置监听
 *
 * Created by 小泽玛丽罗 on 2017/3/1.
 */
@Component("serviceInit")
public class ServiceInit {
    private Logger logger= LoggerFactory.getLogger(ServiceInit.class);
    private static HashMap<String,HashMap<String,List<ServiceNode>>> atomServices=new HashMap<>();
    private static HashMap<String,List<ServiceNode>> combinationServices=new HashMap<>();
    private static List<ApiService> atomApiServices=new ArrayList<>();
    private static List<ApiService> combinationApiServices=new ArrayList<>();
    @Value("${zookeeper.ip}")
    private String zookeeperIp;
    @Value("${zookeeper.path}")
    private String zookeeperPath;
    private final String split="/";

    @Autowired
    private ApiServiceMapper apiServiceMapper;

    //HashMap中key是location，按地理位置来区分服务
    public static HashMap<String, HashMap<String,List<ServiceNode>>> getAtomServices() {
        return atomServices;
    }

    public static HashMap<String, List<ServiceNode>> getCombinationServices() {
        return combinationServices;
    }

    public static List<ApiService> getAtomApiServices(){
        return atomApiServices;
    }
    public static List<ApiService> getCombinationApiServices(){
        return combinationApiServices;
    }
    @PostConstruct
    public void initServiceData(){
        try {
            zookeeperIp= InetAddress.getLocalHost().getHostAddress()+":2181";
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        ZkClient zkClient=new ZkClient(zookeeperIp,5000);
        getLeafNode(zkClient,zookeeperPath);
        logger.info("get ZooKeeper serviceNode and services count is :"+atomServices.size());
        updateDateBase();
    }

    private void getLeafNode(ZkClient zkClient,String path){
        ArrayList<String> atomServiceNodes= (ArrayList<String>)
                zkClient.getChildren(path);
        if(atomServiceNodes.size()>0){
            for(String node:atomServiceNodes){
                getLeafNode(zkClient,path+"/"+node);
            }
        }else {
            try {
                ServiceNode node=zkClient.readData(path,true);
                if(node!=null){
                    addServiceNode(node);
                    logger.info("init add ServiceNode path:"+path);
                    addChildListner(path,zkClient);
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("data type is not ServiceNode");
                logger.error("data type is not ServiceNode path:"+path +e.getMessage(),e);
            }
        }
    }

    /**
     * 增加对节点的监听，数据变更监听，节点删除监听
     * @param nodePath
     * @param zkClient
     */
    private void addChildListner(String nodePath ,ZkClient zkClient){

        //TODO 问题是在服务节点与zk断开连接后，然后再连接上，同时触发了childchanged和datachanged 两个事件
        zkClient.subscribeDataChanges(nodePath, new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {
                logger.info("data changed path:"+dataPath);
                ServiceNode changedServiceNode= (ServiceNode) data;
                HashMap<String,List<ServiceNode>> serviceNodesMap=null;
                List<ServiceNode> serviceNodes=null;
                if(changedServiceNode.isAtomService()){
                    serviceNodesMap=atomServices.get(changedServiceNode.getLocation());
                    serviceNodes=serviceNodesMap.get(changedServiceNode.getServiceCategory());
                }else
                    serviceNodes=combinationServices.get(changedServiceNode.getServiceCategory());
                for(int i=0;i<serviceNodes.size();i++){
                    if(serviceNodes.get(i).getRegistePath().equals(dataPath)){
                        serviceNodes.set(i,changedServiceNode);
                        logger.info("update ServiceNode path:"+dataPath);
                    }
                }
            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
               //IZkDataListener的该方法的监听和IZkChildListener中的对节点删除的监听重复，所以这里
                //对节点删除的操作放到IZkChildListener当中去
            }
        });
        //增加节点时需要监听
        String parentPath=getParentPath(nodePath,"/");
        zkClient.subscribeChildChanges(parentPath, new IZkChildListener() {
            @Override
            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {

                updateServiceNodes(parentPath,currentChilds,zkClient);
            }
        });
    }

    /**
     * 监听当前子节点的变化，新增或删除节点
     * @param dataPath
     */
    private void updateServiceNodes(String dataPath
            ,List<String> currentChilds,ZkClient zkClient){

        if(dataPath.indexOf("atom")!=-1)
            updateAtomServiceNodes(dataPath,currentChilds,zkClient);
        else
            updateCombinationServiceNodes(dataPath,currentChilds,zkClient);
    }

    private void updateAtomServiceNodes(String dataPath
            ,List<String>currentChilds,ZkClient zkClient){
        List<ServiceNode> serviceNodes=null;
        if(currentChilds==null||currentChilds.size()==0){
            //delete 内存中中对应lcoation>category的ServiceNodes
            Iterator iterator=atomServices.entrySet().iterator();
            while(iterator.hasNext()){
                Map.Entry entry= (Map.Entry) iterator.next();
                if(dataPath.indexOf((String) entry.getKey())!=-1){
                    HashMap<String,List<ServiceNode>> nodesMap= (HashMap<String, List<ServiceNode>>) entry.getValue();
                    Iterator iteratorNodes=nodesMap.entrySet().iterator();
                    while(iteratorNodes.hasNext()){
                        Map.Entry entryNodes= (Map.Entry) iteratorNodes.next();
                        if(dataPath.indexOf((String) entryNodes.getKey())!=-1){
                            serviceNodes= (List<ServiceNode>) entryNodes.getValue();
                            serviceNodes.clear();
                        }
                    }
                }
            }
        }else{
            //这里假设在绝大多数情况下currentChild中的值是存在
            String serviceNodePath=dataPath.concat("/").concat(currentChilds.get(0));
            ServiceNode serviceNode=zkClient.readData(serviceNodePath,true);
            //TODO 验证对应
            serviceNodes=atomServices.get(serviceNode.getLocation()).get(serviceNode.getServiceCategory());
            serviceNodes.clear();
            for(String path:currentChilds){
                serviceNodePath=dataPath.concat("/").concat(path);
                ServiceNode serviceNodeNew=zkClient.readData(serviceNodePath);
                serviceNodes.add(serviceNodeNew);
            }
        }
    }

    private void updateCombinationServiceNodes(String dataPath
            ,List<String> currentChilds,ZkClient zkClient){

    }

    private String getParentPath(String nodePath,String split){
        if(null!=nodePath){
            String pa=nodePath.substring(0,nodePath.lastIndexOf(split));
            return nodePath.substring(0,nodePath.lastIndexOf(split));
        }else{
            return null;
        }
    }

    private void addServiceNode(ServiceNode node){
        HashMap<String,List<ServiceNode>> serviceNodesMap=null;
        List<ServiceNode> serviceNodes=null;
        if(node.isAtomService()){
            if(atomServices.containsKey(node.getLocation())){
                serviceNodesMap=atomServices.get(node.getLocation());
                if(serviceNodesMap.containsKey(node.getServiceCategory())){
                    serviceNodes=serviceNodesMap.get(node.getServiceCategory());
                    serviceNodes.add(node);
                }else{
                    serviceNodes=new ArrayList<>();
                    serviceNodes.add(node);
                    serviceNodesMap.put(node.getServiceCategory(),serviceNodes);
                }
            }else{
                serviceNodesMap=new HashMap<>();
                serviceNodes=new ArrayList<>();
                serviceNodes.add(node);
                serviceNodesMap.put(node.getServiceCategory(),serviceNodes);
                atomServices.put(node.getLocation(),serviceNodesMap);
            }
        }else{
            if(combinationServices.containsKey(node.getServiceCategory())){
               serviceNodes=combinationServices.get(node.getServiceCategory());
               serviceNodes.add(node);
            }else{
                serviceNodes=new ArrayList<>();
                serviceNodes.add(node);
                combinationServices.put(node.getServiceCategory(),serviceNodes);
            }
        }
    }


    public static List<ServiceNode> getAtomServiceNodes(){
        List<ServiceNode> atomServiceNodes=new ArrayList<>();
        Iterator iterator=atomServices.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry entry= (Map.Entry) iterator.next();
            Map<String ,List<ServiceNode>> nodesMap= (Map<String, List<ServiceNode>>) entry.getValue();
            Iterator iteratorSub=nodesMap.entrySet().iterator();
            while(iteratorSub.hasNext()){
                Map.Entry entrySub= (Map.Entry) iteratorSub.next();
                List<ServiceNode> serviceNodes= (List<ServiceNode>) entrySub.getValue();
                atomServiceNodes.addAll(serviceNodes);
            }
        }
        return atomServiceNodes;
    }

    public static List<ServiceNode> getCombinationServiceNodes(){
        List<ServiceNode> combinationServiceNodes=new ArrayList<>();
        Iterator iterator=combinationServices.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry entry= (Map.Entry) iterator.next();
            List<ServiceNode> serviceNodes= (List<ServiceNode>) entry.getValue();
            combinationServiceNodes.addAll(serviceNodes);
        }
        return combinationServiceNodes;
    }


    private void updateDateBase(){
        List<ServiceNode> serviceNodeList=getAtomServiceNodes();
        serviceNodeList.addAll(getCombinationServiceNodes());
        for(ServiceNode serviceNode:serviceNodeList){
            try {
                List<ApiService> apiServices=apiServiceMapper.findServices(serviceNode);
                ApiService apiService=null;
                if(apiServices==null||apiServices.size()==0){
                    int id=apiServiceMapper.addApiService(serviceNode2ApiService(serviceNode,null));

                }else{
                    apiServiceMapper.updateService(serviceNode2ApiService(serviceNode,apiServices.get(0)));
                }
                apiServices=apiServiceMapper.findServices(serviceNode);
                if(apiServices!=null&&apiServices.size()!=0
                        &&apiServices.get(0).getServiceType().equals("atom"))
                    atomApiServices.add(apiServices.get(0));
                else if(apiServices!=null&&apiServices.size()!=0
                        &&apiServices.get(0).getServiceType().equals("combination"))
                    combinationApiServices.add(apiServices.get(0));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private ApiService serviceNode2ApiService(ServiceNode serviceNode,ApiService apiService){
        if(apiService==null)
            apiService=new ApiService();
        apiService.setIp(serviceNode.getIp());
        apiService.setPort(serviceNode.getPort());
        apiService.setCategory(serviceNode.getServiceCategory());
        apiService.setServiceName(serviceNode.getName());
        apiService.setServiceType(serviceNode.getType());
        apiService.setRegisterPath(serviceNode.getRegistePath());
        return apiService;
    }
}
